package com.nengchuan.eic.nceconomy.util;

import cn.hutool.core.collection.ConcurrentHashSet;

import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 该ID算法借鉴了Twitter的Snowflake算法实现单机高效有序ID
 * <br>
 * 算法的结构如下(每部分用-分开):<br>
 * <br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 0000000000 00 <br>
 * <br>
 * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0<br>
 * <br>
 * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截)
 * 得到的值），这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下下面程序IdSequence类的startTime属性）。41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * <br>
 * 22位序列，毫秒内的计数，22位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生4194303个ID序号<br>
 * 加起来刚好64位，为一个Long型。<br>
 *
 * 该算法优点是，整体上按照时间自增排序,效率较高,不依赖其他中间件,使数据库的关联关系在web端就确定了,增加了batch insert的效率
 *
 * @author panwen
 * @Date: 2020-05-12
 *
 */
public class IdSequence {

    //起始时间戳
    private static final long startTime = 1519740777809L;

    //序列号占用的位数：22（表示只允许增长序列的范围为：0-4194303）
    private static final long sequenceBits = 22L;

    //22位增长序列的最大值
    private final static long maxSequence=-1L ^ (-1L << sequenceBits);

    //序列号,如果生成时间戳在同一时间内,则增长该序列号
    private static long sequence = 0L;

    //上一次生成ID的时间戳
    private static long lastTimestamp = -1L;


    //生成ID
    public synchronized static long generateId(){
        long timestamp=currTimeMillis();
        if(timestamp<lastTimestamp){
            throw new RuntimeException("生成ID出现时钟回拨");
        }

        //如果当前时间与上一次时间一致,则将自增长序列号加1,否则重置序列号
        if(timestamp==lastTimestamp){
            sequence=(sequence+1)&maxSequence;
            if(sequence==0){
                timestamp = tilNextMillis(lastTimestamp);
            }
        }else{
            sequence=0L;
        }

        lastTimestamp=timestamp;
        return ((timestamp - startTime) << sequenceBits) | sequence;
    }

    /**
     * 阻塞至下一个时间戳
     * @param lastTimestamp 上一时间戳
     * @return 下一时间戳
     */
    private static long tilNextMillis(long lastTimestamp){
        long timestamp = currTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = currTimeMillis();
        }

        return timestamp;
    }

    /**
     * 获取当前时间戳
     * @return 返回时间
     */
    private static long currTimeMillis(){
        return System.currentTimeMillis();
    }


    public static void main(String[] args) throws InterruptedException {
        //用来存储ID,如果Set中的数量与存入时一致,则无重复ID
        Set<Long> set=new ConcurrentHashSet<>();

        //生成10个线程,模拟多线程并发生成ID场景
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        CountDownLatch cdl=new CountDownLatch(10);
        for (int i = 0; i <10 ; i++) {
            executorService.submit(()->{
                for (int j = 0; j <10 ; j++) {
                    set.add(generateId());
                }
                cdl.countDown();
            });
        }


        cdl.await();
        System.err.println(set);

    }
}
